/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package elaborate.h2x.transform.position;

import elaborate.h2x.parser.QNameUtil;
import elaborate.h2x.transform.Relation;
import elaborate.h2x.transform.TransformingModel;
import elaborate.h2x.transform.TransformingNode;
import elaborate.h2x.transform.TransformingNodeId;
import elaborate.h2x.transform.position.schema.state.DefaultStateParsersImpl;
import elaborate.h2x.transform.position.schema.state.ElementState;
import elaborate.h2x.transform.position.schema.state.PseudoState;
import elaborate.h2x.transform.position.schema.state.State;
import elaborate.h2x.transform.position.schema.statematcher.DefaultStateMatcherImpl;
import elaborate.h2x.transform.position.schema.statematcher.StateMatcher;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import juixb.schema.ElementNode;
import juixb.schema.OperatorNode;
import juixb.schema.SchemaModel;
import juixb.schema.SchemaModelBuilder;
import juixb.schema.xsd.xmlbeans.XmlBeansSchemaModelBuilderFactory;

/**
 * apply natural position
 * @author lendle
 */
public class SchemaBasedTransformingNodesPositionerImpl implements TransformingNodesPositioner {

    public void determinePositions(TransformingModel model, PositionMap map) throws Exception {
        List<TransformingNodeId> roots=map.getRoots();
        for(TransformingNodeId rootId : roots){
            TransformingNode root=model.getNode(rootId);
            XmlBeansSchemaModelBuilderFactory factory=
                    new XmlBeansSchemaModelBuilderFactory(root.getAsRootSetting().getSchema(), true, false);
            SchemaModelBuilder schemaModelBuilder=factory.newSchemaModelBuilder();
            SchemaModel schemaModel=schemaModelBuilder.buildSchemaModel(QNameUtil.getQName(root.getDef().getNode()));
            this.positionChildNodes(model, map, schemaModel, root, root, schemaModel.getRootNode());
        }
    }

    /**
     * position child nodes under the given parentNode according to ordering
     * acceptable by schema if no indicated position specified
     * @param model
     * @param map
     * @param rootNode
     * @param parentNode
     */
    private void positionChildNodes(TransformingModel model, PositionMap map, SchemaModel schemaModel, TransformingNode rootNode, TransformingNode parentNode, ElementNode parentElementNode){
        List<TransformingNodeId> childNodes=map.getOrderedChildList(parentNode.getId());
        //System.out.println("parentElementNode="+parentElementNode);
        if(childNodes==null || parentElementNode==null){
            //System.out.println(parentNode.getId()+":"+childNodes+":"+parentElementNode);
            return;
        }

        boolean requireCheck=false;
        for(TransformingNodeId nodeId : childNodes){
            TransformingNode node=model.getNode(nodeId);
            List<Relation> relationList=node.getRelationList();
            //System.out.println("\t"+node.getDef().getNode()+":"+relationList+":"+childNodes.size());
            if(relationList==null || relationList.isEmpty()){
                requireCheck=true;
            }
            if(requireCheck){
                break;
            }
        }
        OperatorNode operatorNode=(OperatorNode) parentElementNode.getFirstChild();//this must be an OperatorNode
        PseudoState state=(PseudoState) new DefaultStateParsersImpl().parseState(operatorNode);
        Map<QName, ElementNode> childElementMap=getChildQNameElementNodeMap(state);
        
        if(requireCheck){
            //if all child node are explicitly positioned, no need to do schema position
            //otherwise, the step is needed
            StateMatcher matcher=new DefaultStateMatcherImpl();
            List<TransformingNodeId> ret=matcher.matchState(state, model, childNodes);
            //System.out.println("\t"+ret.size()+":"+Arrays.deepToString(ret.toArray()));
            if(ret.isEmpty()==false){
                //a valid sequence is found
                TransformingNodeId prevNodeId=null;
                for(TransformingNodeId nodeId : ret){
                    //System.out.println("\tposition: "+nodeId+": prev="+prevNodeId);
                    try {
                        //position the node
                        map.positionNode(parentNode.getId(), (prevNodeId!=null)?prevNodeId:null, nodeId);
                        prevNodeId=nodeId;
                    } catch (Exception ex) {
                        Logger.getLogger(SchemaBasedTransformingNodesPositionerImpl.class.getName()).log(Level.SEVERE, null, ex);
                        throw new RuntimeException(ex);
                    }
                }
            }
            /*if(parentElementNode.getChildCount()!=0){
                System.out.println("\t"+state.getMin()+":"+state.getMax()+":"+Arrays.deepToString(state.getFirstSet().toArray())+":"+Arrays.deepToString(state.getLastSet().toArray()));
                System.out.println("\t"+Arrays.deepToString(childNodes.toArray()));
                state=(PseudoState) state.getChildStates().get(0);
                ElementState elementState=(ElementState) state.getChildStates().get(0);
                System.out.println(elementState.getElementNode().getDisplayName());
            }*/
            /*Enumeration<SchemaTreeNode> en=parentElementNode.children();
            while(en.hasMoreElements()){
                SchemaNode schemaNode=(SchemaNode) en.nextElement();
                System.out.println(schemaNode.getDisplayName());
            }*/
        }
        else{
            //System.out.println("not required");
        }
        //recursively process child nodes
        for(TransformingNodeId nodeId : childNodes){
            //System.out.println("child node: "+nodeId);
            TransformingNode node=model.getNode(nodeId);
            ElementNode elementNode=childElementMap.get(QNameUtil.getQName(node.getDef().getNode()));
            if(elementNode!=null && elementNode.getChildCount()>0){
                this.positionChildNodes(model, map, schemaModel, rootNode, node, elementNode);
            }
        }
    }
    
    private Map<QName, ElementNode> getChildQNameElementNodeMap(PseudoState state){
        Map<QName, ElementNode> map=new HashMap<QName, ElementNode>();
        for(State childState : state.getChildStates()){
            if(childState instanceof ElementState){
                ElementState elementState=(ElementState) childState;
                map.put(elementState.getElementNode().getQName(), elementState.getElementNode());
            }
            else{
                map.putAll(this.getChildQNameElementNodeMap((PseudoState)childState));
            }
        }
        return map;
    }
//    private void positionSubTree(TransformingModel model, PositionMap positionMap, TransformingNode parentNode, TransformingNode prev, TransformingNode node) throws Exception{
//        if(parentNode!=null && node.isIncludedInResult()){
//            positionMap.positionNode(parentNode.getId(), (prev != null) ? prev.getId() : null, node.getId());
//        }
//        List<TransformingNode> childNodes = node.getChildNodes();
//        //System.out.println(childNodes.size());
//        prev = null;
//        if (childNodes != null) {
//            for (TransformingNode childNode : childNodes) {
//                positionSubTree(model, positionMap, (node.isIncludedInResult())?node:parentNode, prev, childNode);
//                if(childNode.isIncludedInResult()){
//                    prev = childNode;
//                }
//            }
//        }
//    }
}
